package com.heima.lock.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.lock.mapper.MyLockMapper;
import com.heima.lock.pojos.MyLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.concurrent.TimeUnit;

/**
 * @author jack
 * @data 2024 10:20
 */
@RestController
public class StockController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private MyLockMapper myLockMapper;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 模拟高并发业务：下单扣库存
     * 结论：synchronized只能锁住单个JVM，不能锁住多个JVM，所以不能解决分布式下的数据安全问题
     *
     * @return
     */
    @GetMapping("/stock")
    public String stock1() {
        synchronized ("lock") {
            //1. 获取当前库存剩余数量
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

            //2. 剩余数量 > 0 : 下单成功，扣减库存,返回：success
            if (stock > 0) {
                stock--;
                System.out.println("恭喜：" + Thread.currentThread().getName() + "下单成功! 剩余库存：" + stock);

                //修改数据库剩余库存数量
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));

                return "success!";
            } else {
                //3. 剩余数量 <= 0 : 下单失败，快速返回：error
                System.out.println("下单失败，库存不足!");
                return "error!";
            }
        }
    }


    /**
     * 模拟高并发业务：下单扣库存
     * 基于mysql的unique唯一索引实现分布式锁
     *
     * @return
     */
    @GetMapping("/stock2")
    public String stock2() {
        //尝试获得锁
        MyLock lock = new MyLock();
        lock.setLockFeild("stockLock");
        lock.setThreadId(String.valueOf(Thread.currentThread().getId()));
        try {
            myLockMapper.insert(lock);
            //a. 成功获取了锁，就可以进行业务处理
            //1. 获取当前库存剩余数量
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

            //2. 剩余数量 > 0 : 下单成功，扣减库存,返回：success
            if (stock > 0) {
                stock--;
                System.out.println("恭喜：" + Thread.currentThread().getName() + "下单成功! 剩余库存：" + stock);

                //修改数据库剩余库存数量
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));

                return "success!";
            } else {
                //3. 剩余数量 <= 0 : 下单失败，快速返回：error
                System.out.println("下单失败，库存不足!");
                return "error!";
            }
        } catch (Throwable e) {
            System.out.println("获取分布式锁失败：" + Thread.currentThread().getId());
            //b. 获取锁失败，不可以进行业务处理，直接进行快速失败响应
            return "server is busy!";
        } finally {
            //释放锁  -- 注意：只能是谁获得锁，谁释放锁，没有获得锁的线程不能释放别人的锁
            if (lock.getId() != null) {
                myLockMapper.deleteById(lock.getId());
            }

        }

    }


    /**
     * 模拟高并发业务：下单扣库存
     * 基于redis的setNx命令 实现分布式锁
     *
     * @return
     */
    @GetMapping("/stock3")
    public String stock4() {
        try {
            //尝试获得锁
            Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent("myLock", String.valueOf(Thread.currentThread().getId()));

            //a. 成功获取了锁，就可以进行业务处理
            if (isLock) {
                //1. 获取当前库存剩余数量
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

                //2. 剩余数量 > 0 : 下单成功，扣减库存,返回：success
                if (stock > 0) {
                    stock--;
                    System.out.println("恭喜：" + Thread.currentThread().getName() + "下单成功! 剩余库存：" + stock);

                    //修改数据库剩余库存数量
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));

                    return "success!";
                } else {
                    //3. 剩余数量 <= 0 : 下单失败，快速返回：error
                    System.out.println("下单失败，库存不足!");
                    return "error!";
                }
            } else {
                //b. 获取锁失败，不可以进行业务处理，直接进行快速失败响应
                System.out.println("获取分布式锁失败：" + Thread.currentThread().getId());
                return "server is busy!";
            }
        } finally {
            //释放锁  -- 注意：只能是谁获得锁，谁释放锁，没有获得锁的线程不能释放别人的锁
            String threadId = stringRedisTemplate.opsForValue().get("myLock");
            String currentThreadId = String.valueOf(Thread.currentThread().getId());
            if (threadId != null && threadId.equals(currentThreadId)) {
                stringRedisTemplate.delete("myLock");
            }

        }

    }


    /**
     * 模拟高并发业务：下单扣库存
     * 基于redisson实现，本质还是redis的setNx命令 实现分布式锁,只不过功能封装的更强大
     * 1. watchDog看门狗机制
     * 2. 提供了公平锁/非公平锁实现
     * 3. 提供了阻塞锁/非阻塞锁实现
     *
     * @return
     */
    @GetMapping("/stock5")
    public String stock5() {
        //尝试获得锁
        RLock rLock = redissonClient.getLock("myLock");     //注意：这里并没有加锁，仅仅是创建了一个java对象

        try {
            //阻塞锁: 尝试获取锁，如果获取到了，则继续代码向下执行，取消阻塞。如果获取锁失败，则线程会在这里进行等待，不断尝试重新获得锁
            //非阻塞锁: 尝试获取锁，不管有没有获取到，代码都会继续向下执行。
            //rLock.lock();       //这里才是真正的去尝试获得锁，底层执行的是setNx,这里采用的是阻塞锁实现。
            boolean isLock = rLock.tryLock();       //这里才是真正的去尝试获得锁，底层执行的是setNx,这里采用的是非阻塞锁实现。

            //a. 成功获取了锁，就可以进行业务处理
            if (isLock) {
                //1. 获取当前库存剩余数量
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

                //2. 剩余数量 > 0 : 下单成功，扣减库存,返回：success
                if (stock > 0) {
                    stock--;
                    System.out.println("恭喜：" + Thread.currentThread().getName() + "下单成功! 剩余库存：" + stock);

                    //修改数据库剩余库存数量
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));

                    return "success!";
                } else {
                    //3. 剩余数量 <= 0 : 下单失败，快速返回：error
                    System.out.println("下单失败，库存不足!");
                    return "error!";
                }
            } else {
                //b. 获取锁失败，不可以进行业务处理，直接进行快速失败响应
                System.out.println("获取分布式锁失败：" + Thread.currentThread().getId());
                return "server is busy!";
            }
        } finally {
            //释放锁  -- 注意：只能是谁获得锁，谁释放锁，没有获得锁的线程不能释放别人的锁
            try {
                rLock.unlock();
            } catch (Exception e) {
            }
        }

    }


    /**
     * 模拟高并发业务：下单扣库存
     * 基于redisson实现，本质还是redis的setNx命令 实现分布式锁,只不过功能封装的更强大
     * 1. watchDog看门狗机制
     * 2. 提供了公平锁/非公平锁实现
     * 3. 提供了阻塞锁/非阻塞锁实现
     *
     * @return
     */
    @GetMapping("/stock6")
    public String stock6() {
        //尝试获得锁
        RLock rLock = redissonClient.getLock("myLock");     //注意：这里并没有加锁，仅仅是创建了一个java对象

        try {
            //阻塞锁: 尝试获取锁，如果获取到了，则继续代码向下执行，取消阻塞。如果获取锁失败，则线程会在这里进行等待，不断尝试重新获得锁
            //非阻塞锁: 尝试获取锁，不管有没有获取到，代码都会继续向下执行。
            rLock.lock();       //阻塞锁实现。
            //boolean isLock = rLock.tryLock();       //非阻塞锁实现。

            //a. 成功获取了锁，就可以进行业务处理

            //1. 获取当前库存剩余数量
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

            //2. 剩余数量 > 0 : 下单成功，扣减库存,返回：success
            if (stock > 0) {
                stock--;
                System.out.println("恭喜：" + Thread.currentThread().getName() + "下单成功! 剩余库存：" + stock);

                //修改数据库剩余库存数量
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));

                return "success!";
            } else {
                //3. 剩余数量 <= 0 : 下单失败，快速返回：error
                System.out.println("下单失败，库存不足!");
                return "error!";
            }
        } finally {
            //释放锁  -- 注意：只能是谁获得锁，谁释放锁，没有获得锁的线程不能释放别人的锁
            try {
                rLock.unlock();
            } catch (Exception e) {
            }
        }

    }

    /**
     * 模拟高并发业务：下单扣库存
     * 基于redisson实现，本质还是redis的setNx命令 实现分布式锁,只不过功能封装的更强大
     * 1. watchDog看门狗机制           lock() / tryLock() 括号内不要指定锁的过期时间
     * 2. 提供了公平锁/非公平锁实现    redissonClient.getFairLock("myLock")  / redissonClient.getLock("myLock")
     * 3. 提供了阻塞锁/非阻塞锁实现    lock() / tryLock()
     *
     * @return
     */
    @GetMapping("/stock7")
    public String stock7() {
        //尝试获得锁 -- 公平锁实现
        RLock rLock = redissonClient.getFairLock("myLock");     //注意：这里并没有加锁，仅仅是创建了一个java对象

        try {
            //a. 成功获取了锁，就可以进行业务处理
            //rLock.lock(5, TimeUnit.SECONDS);        // 当前线程如果获取到锁，那么这个锁有效期只有5秒，5秒之后

            // 如果我们不设置锁的有效期，默认底层是30秒，并且开启看门狗watchDog线程对锁进行监听，每当锁的时间消耗10s，
            // watchDog线程会校验一下当前线程是否仍然存活，如果仍然存活会对锁进行自动续期操作，继续恢复到30s
            rLock.lock();

            try {
                Thread.sleep(20000);
            } catch (Exception e) {
                e.printStackTrace();
            }

            //1. 获取当前库存剩余数量
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));

            //2. 剩余数量 > 0 : 下单成功，扣减库存,返回：success
            if (stock > 0) {
                stock--;
                System.out.println("恭喜：" + Thread.currentThread().getName() + "下单成功! 剩余库存：" + stock);

                //修改数据库剩余库存数量
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));

                return "success!";
            } else {
                //3. 剩余数量 <= 0 : 下单失败，快速返回：error
                System.out.println("下单失败，库存不足!");
                return "error!";
            }
        } finally {
            //释放锁  -- 注意：只能是谁获得锁，谁释放锁，没有获得锁的线程不能释放别人的锁
            try {
                rLock.unlock();
            } catch (Exception e) {
            }
        }

    }
}
